﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GMC_4
{
    class CompileException : Exception
    {
        public CompileException(string format, params object[] args)
            : base(string.Format(format, args))
        {
        }
    }

    class Assembler
    {

        public static byte[] Assemble(string src)
        {
            Assembler compiler = new Assembler(src);
            return compiler.Assemble();
        }

        string[] tokens;
        byte[] code = new byte[Vm.MemorySize];
        Dictionary<string, int> labels = new Dictionary<string, int>();
        Dictionary<int, string> jumps = new Dictionary<int, string>();
        int length = 0;

        Assembler(string src)
        {
            string[] lines = src.ToLowerInvariant().Split(new string[] { Environment.NewLine, "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < lines.Length; ++i)
            {
                int commentIdx = lines[i].IndexOf(";");
                if (commentIdx != -1)
                    lines[i] = lines[i].Substring(0, commentIdx);
            }
            tokens = string.Join(" ", lines).Split(new string[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < code.Length; ++i)
                code[i] = 0xf;
        }

        byte[] Assemble()
        {
            int i = 0;
            while (i != tokens.Length)
            {
                string token = tokens[i++];
                if (token.EndsWith(":"))
                {
                    string strippedToken = token.Substring(0, token.Length - 1);
                    byte value;
                    if (byte.TryParse(strippedToken, System.Globalization.NumberStyles.HexNumber, null, out value))
                    {
                        length = value;
                    }
                    else
                    {
                        if (labels.ContainsKey(token))
                            throw new CompileException("Multiple instance of label '{0}' found", token);
                    }
                    labels.Add(strippedToken, length);
                    continue;
                }

                byte op;
                if (!Ops.OpTable.TryGetValue(token, out op))
                {
                    byte value;
                    if (!byte.TryParse(token, System.Globalization.NumberStyles.HexNumber, null, out value))
                        throw new CompileException("Unrecognized token '{0}' found", token);
                    code[length++] = value;
                    continue;
                }
                code[length++] = op;

                switch ((OpCode)op)
                {
                    case OpCode.CAL:
                        {
                            byte value;
                            string subroutine = tokens[i++];
                            if (!Ops.SubroutineTable.TryGetValue(subroutine, out value))
                                throw new CompileException("Unrecognized subroutine '{0}' found", subroutine);
                            code[length++] = value;
                        }
                        break;

                    case OpCode.JUMP:
                        string label = tokens[i++];
                        jumps.Add(length, label);
                        code[length++] = 0;
                        code[length++] = 0;
                        break;

                    default:
                        for (int j = 0; j < Ops.ParamCounts[op]; ++j, ++i)
                        {
                            byte value;
                            if (!byte.TryParse(tokens[i], System.Globalization.NumberStyles.HexNumber, null, out value))
                                throw new CompileException("Expected token '{0}' to be a hexadecimal number", tokens[i]);
                            code[length++] = value;
                        }
                    break;
                }
            }

            // Patch jumps
            foreach (var kv in jumps)
            {
                int jumpAddress = kv.Key;
                string label = kv.Value;
                int tagetAddress;
                if (!labels.TryGetValue(label, out tagetAddress))
                    throw new CompileException("Can't find label of label '{0}'", label);
                code[jumpAddress] = (byte)((tagetAddress >> 4) & 0xf);
                code[jumpAddress + 1] = (byte)(tagetAddress & 0xf);
            }

            return code.ToArray();
        }
    }
}
